import 'dart:async';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';

import 'package:path/path.dart' as p;
import 'package:file_selector/file_selector.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:starkylin_file_shared/src/server_util.dart';
import 'package:starkylin_file_shared/src/socket/socket_client.dart';
import 'package:starkylin_file_shared/src/socket/socket_server.dart';
import 'package:starkylin_file_shared/src/utils/get_file_size.dart';
import 'package:starkylin_file_shared/type/type.dart';

import 'starkylin_file_shared_platform_interface.dart';
import './src/utils/general.dart';

export './type/type.dart';

class StarkylinFileShared implements StarkylinFileSharedPlatform {
  final int fileServerPort = 26990;
  final Dio dio = Dio();
  final Map<int, DownloadInfo> progressInfo = {};
  final SocketServer _server = SocketServer();
  final SocketClient _client = SocketClient();
  final Map<ClientMsgCallback, void Function(String)?> msgListenersMap = {};

  // 根据msgGroupId分组存储消息
  final Map<int, List<MessageInfo>> clientMsgMap = {};
  final Map<int, List<MessageInfo>> serverMsgMap = {};

  String clientIp = '';

  String defaultSavePath = '';
  StarkylinFileShared() {
    if (defaultTargetPlatform == TargetPlatform.android) {
      defaultSavePath = '/storage/emulated/0/Download';
    } else if (defaultTargetPlatform == TargetPlatform.windows) {
      defaultSavePath = p.dirname(Platform.resolvedExecutable);
    } else if (defaultTargetPlatform == TargetPlatform.linux) {
      defaultSavePath = "/home/";
    }
    defaultSavePath = Directory.current.path;
    // TODO:需要关注一下Linux和安卓端的表现
    log("defaultSavePath: $defaultSavePath");
    clientIp = "localhost";
    test(clientIp);
  }

  // 检查消息完整性
  void checkMsgPackage(int groupIp) {
    int count = 0;
    Timer.periodic(const Duration(milliseconds: 300), (timer) {
      if (clientMsgMap[groupIp] == null) {
        timer.cancel();
        return;
      }
      List<MessageInfo> list = clientMsgMap[groupIp]!;
      count++;

      if (count >= 5) {
        timer.cancel();

        for (var cb in msgListenersMap.values) {
          cb?.call("接收消息超时");
          clientMsgMap.remove(groupIp);
        }
      } else if (list.length == list[0].total) {
        timer.cancel();
        log("length == total, total: ${list[0].total}");

        for (var cb in msgListenersMap.keys) {
          var pack = MsgPack.fromMsgInfo(list);
          if (pack != null) {
            pack.url = "http://$clientIp:$fileServerPort";
            cb(pack);
          }
          clientMsgMap.remove(groupIp);
        }
      }
    });
  }

  @override
  void addMessageListener(ClientMsgCallback cb,
      {void Function(String err)? onError}) {
    if (!msgListenersMap.containsKey(cb)) {
      msgListenersMap[cb] = onError;
    }
  }

  @override
  void removeMessageListener(ClientMsgCallback cb) {
    msgListenersMap.remove(cb);
  }

  void test(String ip) {
    _server.serverBind(fileServerPort + 1, handler: (msg) {
      log("startserver msg $msg");
      // MessageInfo? msgInfo = MessageInfo.fromJson(msg);
      // if (msgInfo != null) serverReceived?.call(msgInfo);
    });
    _client.clientConnect(ip, fileServerPort + 1, handler: (msg) {
      MessageInfo? msgInfo = msg is String ? MessageInfo.fromJson(msg) : msg;
      if (msgInfo != null) {
        var msgGroupId = msgInfo.msgGroupId;
        if (clientMsgMap[msgGroupId] == null) {
          clientMsgMap[msgGroupId] = [];
          checkMsgPackage(msgGroupId);
        }

        clientMsgMap[msgGroupId]!.add(msgInfo);
      }
    });
  }

  Future<void> startServer({void Function(dynamic)? handler}) async {
    await _server.serverBind(fileServerPort + 1, handler: handler);
  }

  Future<void> stopServer() async {
    await _server.stopServer();
  }

  Future<void> connectToServer(String ip,
      {void Function(dynamic)? handler}) async {
    _client.clientConnect(ip, fileServerPort + 1, handler: (msg) {
      MessageInfo? msgInfo = msg is String ? MessageInfo.fromJson(msg) : msg;
      if (msgInfo != null) {
        var msgGroupId = msgInfo.msgGroupId;
        if (clientMsgMap[msgGroupId] == null) {
          clientMsgMap[msgGroupId] = [];
          checkMsgPackage(msgGroupId);
        }

        clientMsgMap[msgGroupId]!.add(msgInfo);
      }

      handler?.call(msgInfo);
    });
  }

  Future<void> _sendMsg(MessageInfo msg) async {
    if (!_server.isBinding) {
      await _server.serverBind(fileServerPort + 1);
    }
    await _server.connCompleter.future;
    log("msg.toString() ${msg.toString()}");
    _server.serverSendMsg(msg.toString());
  }

  @override
  Future<String?> selectDir() async {
    String? dirPath;
    dirPath = await getDirectoryPath(
      confirmButtonText: 'Select',
    );

    log("dirPath: $dirPath");
    return dirPath;
  }

  Future<void> sendDirFromPath(String path) async {
    Directory dir = Directory(path);
    int msgGroupId = DateTime.now().microsecondsSinceEpoch;
    List<MessageInfo> msgList = [];
    int index = 0;
    String dirName = p.basename(path);
    Completer? completer;
    int savingCount = 0;
    dir.list(recursive: true).listen((event) async {
      FileSystemEntity entity = event;
      String suffix = '';
      int size = 0;
      var eventPath = event.path;
      log("----------path: $eventPath");
      if (entity is Directory) {
        suffix = '/';
        log("entity is Directory, and path: ${entity.path}");
        eventPath = eventPath.replaceAll('\\', '/');
        eventPath = eventPath.replaceAll(RegExp('^[A-Z]:'), '');
        eventPath = eventPath.replaceAll(RegExp('^/'), '');
      } else if (entity is File) {
        log("entity is File, and path: ${entity.path}");
        size = await entity.length();
        savingCount++;
        completer ??= Completer();
        eventPath = await ServerUtil.serveFile(entity.path, fileServerPort);
        savingCount--;
        if (savingCount <= 0) {
          savingCount = 0;
          completer!.complete();
          completer = null;
        }
      }

      MessageInfo info = MessageInfo(
          path: eventPath + suffix,
          msgGroupId: msgGroupId,
          dirName: dirName,
          type:
              entity is Directory ? FileMessageType.dir : FileMessageType.file,
          fileSize: entity is Directory ? null : size,
          index: index);
      index++;
      log("msgList.add");
      msgList.add(info);
    }, onDone: () async {
      log("onDone ${msgList.length}");
      await completer?.future;
      for (int i = 0; i < msgList.length; i++) {
        msgList[i].total = msgList.length;
        _sendMsg(msgList[i]);
      }
    });
  }

  @override
  Future<List<String>> selectFile() async {
    List<String> filePaths = [];
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      allowCompression: false,
      allowMultiple: true,
    );
    if (result != null) {
      for (PlatformFile file in result.files) {
        filePaths.add(file.path!);
      }
    } else {
      // User canceled the picker
    }
    if (kDebugMode) {
      log("selectFile filePaths: $filePaths");
    }
    return filePaths;
  }

  Future<void> sendFileFromPath(List<String> filePathList) async {
    int total = filePathList.length;
    int msgGroupId = DateTime.now().millisecondsSinceEpoch;
    for (var i = 0; i < total; i++) {
      log("sendFileFromPath: ${filePathList[i]}");
      String filePath = filePathList[i];
      File file = File(filePath);
      // 检查文件是否存在
      bool exists = await file.exists();
      if (exists) {
        // 获取文件大小
        int size = await file.length();
        ServerUtil.serveFile(filePath, fileServerPort);
        // 替换windows的路径分隔符
        filePath = filePath.replaceAll('\\', '/');
        // 替换windows盘符
        filePath = filePath.replaceAll(RegExp('^[A-Z]:'), '');
        p.Context context;
        if (defaultTargetPlatform == TargetPlatform.windows) {
          context = p.windows;
        } else {
          context = p.posix;
        }
        final MessageInfo sendFileInfo = MessageInfo(
          msgGroupId: msgGroupId,
          type: FileMessageType.file,
          path: filePath,
          fileName: context.basename(filePath),
          fileSize: size,
          total: total,
        );

        _sendMsg(sendFileInfo);
      } else {
        log("文件不存在");
      }
    }
  }

  Future<List<XFile>?> getFilesForDesktop() async {
    const typeGroup = XTypeGroup(
      label: 'Select files',
      extensions: null,
    );
    final files = await openFiles(acceptedTypeGroups: [typeGroup]);
    if (files.isEmpty) {
      return null;
    }
    return files;
  }

  @override
  Future<CancelToken?> download(MsgPack pack, {String? saveDir}) async {
    if (pack.pathList.isEmpty) return null;
    if (saveDir == null) {
      saveDir = await getDirectoryPath(
        confirmButtonText: 'Select',
      );
      if (saveDir == null) {
        log("取消下载");
        return null;
      }
    }
    CancelToken token = CancelToken();
    if (pack.type == FileMessageType.file) {
      downloadFile(pack, saveDir, token);
    } else if (pack.type == FileMessageType.dir) {
      downloadDir(pack, saveDir, token);
    }

    return token;
  }

  Future<void> downloadFile(
      MsgPack pack, String? dir, CancelToken token) async {
    if (progressInfo.containsKey(pack.msgGroupId)) {
      if (progressInfo[pack.msgGroupId]!.progress == 1.0) {
        log("下载已完成");
        return;
      } else if (progressInfo[pack.msgGroupId]!.progress != 0.0) {
        log("正在下载中");
        return;
      }
    }
    DownloadInfo info = DownloadInfo();
    progressInfo[pack.msgGroupId] = info;
    Timer timer = await computeNetSpeed(info);
    int downloadSize = 0;
    for (var path in pack.pathList) {
      String url = "${pack.url}$path";
      String savePath = getSavePath(url, dir);
      try {
        await dio.download(
          '$url?download=true',
          savePath,
          cancelToken: token,
          onReceiveProgress: (count, total) {
            log("onReceiveProgress: $count, $total");
            info.count = downloadSize + count;
            info.progress = count / pack.fullSize;
            if (count == total) {
              downloadSize += total;
            }
          },
        );
      } catch (e) {
        log("下载失败: $e");
      }
    }

    timer.cancel();
  }

  Future<void> downloadDir(
      MsgPack pack, String savePath, CancelToken token) async {
    String baseDirPath = '$savePath/${pack.dirName}';
    try {
      log("创建文件夹 $baseDirPath");
      await Directory(baseDirPath).create(recursive: true);
    } catch (e) {
      log("创建文件夹 $baseDirPath 失败:\n $e");
      return;
    }
    DownloadInfo downloadInfo = DownloadInfo();
    progressInfo[pack.msgGroupId] = downloadInfo;
    var timer = await computeNetSpeed(downloadInfo);
    for (var path in pack.pathList) {
      String relativePath = path.replaceAll(RegExp('.*?${pack.dirName}/'), '/');
      if (path.endsWith("/")) {
        try {
          log("创建文件夹 ${baseDirPath + relativePath}");
          await Directory(baseDirPath + relativePath).create(recursive: true);
        } catch (e) {
          log("创建文件夹 ${baseDirPath + relativePath} 失败:\n $e");
        }
      } else {
        String fullSavePath = '$savePath/${pack.dirName}/$relativePath';
        int downloadSize = 0;
        if (pack.fullSize == 0) return;
        await dio.download(
          '${pack.url}$path?download=true',
          fullSavePath,
          cancelToken: token,
          onReceiveProgress: (count, total) {
            downloadInfo.count = downloadSize + count;
            downloadInfo.progress = count / pack.fullSize;
            if (count == total) {
              downloadSize += total;
            }
          },
        );
      }
    }
    log("downloadDir end");
    timer.cancel();
  }

  String getSavePath(String url, String? dir) {
    String savePath = '$dir//${p.basename(url)}';
    return getSafePath(savePath);
  }

  String getSafePath(String savePath) {
    if (!File(savePath).existsSync()) {
      return savePath;
    }
    String dirPath = p.dirname(savePath);
    String fileNameWithoutExt = p.basenameWithoutExtension(savePath);
    int count = 1;
    String newPath =
        '$dirPath/$fileNameWithoutExt($count)${p.extension(savePath)}';
    while (File(newPath).existsSync()) {
      count++;
      newPath = '$dirPath/$fileNameWithoutExt($count)${p.extension(savePath)}';
    }
    return newPath;
  }

  // 计算网速
  Future<Timer> computeNetSpeed(DownloadInfo info) async {
    int tmpCount = 0;
    Timer timer;
    timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
      int diff = info.count - tmpCount;
      tmpCount = info.count;
      // 乘以2是因为半秒测的一次
      info.speed = FileSizeUtils.getFileSize(diff * 2);
      log('网速 -> ${info.speed}');
    });
    return timer;
  }
}
